package gate

import (
	"base"
	"emma/errors"
	"emma/protobuf"
	"game_server/chanrpc"
	gate_module "game_server/gate"
	"game_server/logic/center/api"
	"game_server/logic/center/event"
	"game_server/logic/gate/api"
	"game_server/logic/gate/response"
	. "game_server/logic/internal"
	"game_server/model"
	"github.com/golang/protobuf/proto"
	leaf_gate "leaf/gate"
	"proto/base"
	"proto/server/gate"
)

type gateSystem struct {
	servers map[base.AgentId]*gateServer
}

var system = new(gateSystem)

func init() {
	system.servers = make(map[base.AgentId]*gateServer)
}

func init() {
	gate_api.Dial = system.Dial
	gate_api.Close = system.Close
	gate_api.ParseError = system.ParseError
	gate_api.SendMsg = system.SendMsg
	gate_api.WriteMsg = system.WriteMsg
}

func init() {
	Event.Listen(center_event.OnConnect, system.onCenterConnect)
}

func init() {
	Skeleton.AfterInit(func() {
		Skeleton.RegisterChanRPC(chanrpc.NewGateAgent, system.newAgent)
		Skeleton.RegisterChanRPC(chanrpc.CloseGateAgent, system.closeAgent)
	})
}

func (*gateSystem) Dial(agentId base.AgentId, address string) {
	if _, exists := system.getServer(address); exists {
		return
	}

	server := new(gateServer)
	server.id = agentId
	server.address = address

	system.servers[agentId] = server
	gate_module.ChanRpc.Go(chanrpc.ConnectGateServer, address)
}

func (*gateSystem) Close(agentId base.AgentId) {
	server, exists := system.servers[agentId]
	if !exists {
		return
	}

	gate_module.ChanRpc.Go(chanrpc.CloseGateServer, server.address)
	delete(system.servers, agentId)
}

func (*gateSystem) ParseError(err error) (options []gate_response.Option) {
	if err == nil {
		return
	}

	if e, ok := err.(*errors.Error); ok {
		options = append(options, gate_response.WithErrorCode(e.Code()))
		args := e.Args()
		if len(args) > 0 {
			options = append(options, gate_response.WithErrorArgs(args))
		}
	} else {
		code := int(proto_base.ErrorCode_ServerInternalError)
		options = append(options, gate_response.WithErrorCode(code))
	}

	return
}

func (*gateSystem) SendMsg(player *model.Player, msg proto.Message, args ...gate_response.Option) {
	if player == nil {
		base.Logger.Error("player is nil")
		return
	}

	if msg == nil {
		base.Logger.Error("msg is nil")
		return
	}

	if !player.Online() {
		return
	}

	data, e := proto.Marshal(msg)
	if e != nil {
		base.Logger.Error("marshal error, %s", e)
		return
	}

	protoName := protobuf.Name(msg)
	response := new(proto_server_gate.Response)
	response.Name = protoName
	response.Data = data
	response.PlayerId = player.Id().String()

	if len(args) > 0 {
		options := new(gate_response.Options)
		for _, item := range args {
			item.Apply(options)
		}
		response.Code = options.Code
		response.Args = options.Args
	}

	gateId := player.GateId()
	server, exists := system.servers[gateId]
	if !exists || server.Agent == nil {
		return
	}

	server.WriteMsg(response)
}

func (*gateSystem) WriteMsg(gateId base.AgentId, msg proto.Message) {
	server, exists := system.servers[gateId]
	if !exists || server.Agent == nil {
		return
	}

	server.WriteMsg(msg)
}

func (*gateSystem) onCenterConnect() {
	for _, server := range system.servers {
		system.Close(server.id)
	}
}

func (*gateSystem) newAgent(args []interface{}) {
	agent := args[0].(leaf_gate.Agent)
	base.Logger.Debug("new gateway agent, remote address: %s, local address: %s", agent.RemoteAddr(), agent.LocalAddr())

	address := agent.RemoteAddr().String()
	server, exists := system.getServer(address)

	if !exists {
		base.Logger.Error("not connect gate server: %s", address)
		return
	}

	server.Agent = agent
	agent.SetUserData(server)

	system.register(agent)
}

func (*gateSystem) register(agent leaf_gate.Agent) {
	msg := new(proto_server_gate.Register)
	msg.AgentId = uint32(center_api.GameAgentId())

	agent.WriteMsg(msg)
}

func (*gateSystem) closeAgent(args []interface{}) {
	agent := args[0].(leaf_gate.Agent)
	base.Logger.Debug("close gateway agent, remote address: %s, local address: %s", agent.RemoteAddr(), agent.LocalAddr())
}

func (*gateSystem) getServer(address string) (result *gateServer, exists bool) {
	for _, item := range system.servers {
		if item.address == address {
			result = item
			exists = true
			return
		}
	}

	return
}
